ACID transactions and implementation in a PostgreSQL Database - Aviator Blog
WAL 提供了一种机制,对数据库的任何写入,都需要先写入 WAL 文件,后续再将脏页写入缓冲区,写入磁盘。这类似于 Redis 中的 AOF 机制。
这可以保证 ACID 中的 Durability
loading...
SQLite#
sqlite | Write-Ahead Logging
SQLite 中有 Rollback
机制实现类似的事情。其通过将数据库原始内容写入 rollback
文件,将更改直接写入数据库文件。
SQLite 于 3.7.0
版本开始提供 WAL
机制,与 rollback
相反,原始数据仍在数据库中,将更改追加到 WAL
文件中。
这样可以使得原始数据库不被修改,允许其他的读线程进行并发读取。
Postgres#
PostgreSQL: Documentation: 16: 30.3. Write-Ahead Logging (WAL)
WAL: Everything you want to know - YouTube
在没有 WAL 或类似机制的时候,DB 的修改流程如下图所示,当在第四步出现崩溃时,就会出现数据丢失。
检查点#
最终 WAL 中修改的数据还是要写回到数据库文件的。将 WAL 中的事务写入数据库的点称为检查点。
默认情况下,在 WAL 大小达到 1000 页的时候会自动执行检查点。(有需要也可以主动执行)
并发#
WAL 模式下,开启一个读操作时,首先定位到WAL
中最后一个合法 commit 的位置,称为结束标记。每个读线程都可以有自己的结束标记。在一个读线程的读取事务中,其所看到的内容不变(结束标记不变)。
当读取器需要一页内容时,它首先检查 WAL 中是否存在该页,如果是,它会提取 WAL 中结束标记之前该页面的最后一个副本。如果在结束标记前 WAL 中不存在该页面的副本,则从原始数据库文件中读取该页面。
因为读线程相互独立,为了避免每个读线程都扫描整个 WAL 文件,内存中还维护了一个 共享的 wal-index
数据结构,可以让读线程快速定位页面,减少 I/O。(这要求所有的读线程位于同一个机器上,因此 WAL 不能用于 NFS)
检查点执行时,将 WAL 文件中的内容同步到数据库文件。但是当检查点执行必须停止于当前读线程中最早的那个结束标记,防止覆盖读线程正在使用的部分。在 wal-index
中会记录同步中断的位置,在下一次检查点时继续同步。
loading...
因此长时间运行的读事务会阻塞检查点线程,但最终读事务会结束,检查点线程可以继续同步。发生写操作时,写线程查看检查点的同步进度。如果所有 WAL 内容都同步到了数据库且不存在使用 WAL 的读线程,写线程会将 WAL 清空,再开始写入新事务。可以防止 WAL 无限增长。
瓶颈#
因为其对并发的限制,在写入密集型的应用中,性能会受到制约,在现代 SSD 中,这种限制显得尤为明显。
MySQL :: MySQL 8.0:新的无锁、可扩展的 WAL 设计